经由 fork() 创建的子进程,其中只有一个线程。子进程里仅存的线程,对应着主进程里调用 fork() 函数的线程。此外,主进程的整个虚存空间都被复制到了子进程。因而,包括互斥锁、条件变量、其余线程的内部对象等,都保持着原样。由此引发的问题,可以考虑用 pthread_atfork() 函数解决。
参考自:https://liam.page/2017/01/17/fork-safe/
#include
#include
#include
#include
#include
#include
/* 功能说明:* 主函数 ---- ------main:主线程---> 打印:main:主线程id=..* 工作线程 --fork() ---fun:父进程---> 打印: fun:父进程id=.. * |---fun:子进程---> 打印: fun: 子进程id=..*/void *Thread_fun()
{pid_t pid &#61; fork();if(pid !&#61; 0) // 父进程&#xff1a;pid等于子进程id{for(int i &#61; 0; i < 3; i&#43;&#43;){printf(" fun::父进程 【%d】:id &#61; %d\n",i&#43;1,getpid());sleep(1);}}else{for(int i &#61; 0; i < 3; i&#43;&#43;){printf(" fun::子进程 【%d】:id &#61; %d\n",i&#43;1,getpid());sleep(1);}}
}int main()
{pthread_t id; // 创建线程并调用工作线程int res &#61; pthread_create(&id,NULL,Thread_fun,NULL);assert(res &#61;&#61; 0); // 返回值为0&#xff0c;表示创建成功for(int i &#61; 0; i < 3; i&#43;&#43;){printf("main::主线程 【%d】:id &#61; %d\n",i&#43;1,getpid());sleep(1);}printf("main is stop ... \n");// 等待工作线程结束&#xff0c;在此阻塞pthread_join(id,NULL);exit(0);
}
fork产生的子进程中只有一个线程。
执行结果&#xff1a;
main::主线程 【1】:id &#61; 74226
fun::父进程 【1】:id &#61; 74226
fun::子进程 【1】:id &#61; 74228
fun::父进程 【2】:id &#61; 74226
main::主线程 【2】:id &#61; 74226
fun::子进程 【2】:id &#61; 74228
fun::父进程 【3】:id &#61; 74226
main::主线程 【3】:id &#61; 74226
fun::子进程 【3】:id &#61; 74228
main is stop …
如果我们把输出屏幕看成一块共享资源的话&#xff0c;我们想要输出结果为&#xff0c;先让main中的打印完成&#xff0c;在打印线程内的。则我们需要加锁完成。
把临界区代码加锁。&#xff08;需要注意的是&#xff0c;fork会复制父进程加锁状态&#xff09;&#xff0c;如果我们代码这样写。
include <stdio.h>
#include
#include
#include
#include
#include
/* 功能说明&#xff1a;* 主函数 ---- ------main:主线程---> 打印&#xff1a;main&#xff1a;主线程id&#61;..* 工作线程 --fork() ---fun&#xff1a;父进程---> 打印&#xff1a; fun&#xff1a;父进程id&#61;.. * |---fun&#xff1a;子进程---> 打印&#xff1a; fun: 子进程id&#61;..** 加锁&#xff1a;保证先让main中的打印先完成*/void *Thread_fun()
{sleep(1); // 保证让主进程先获得锁pid_t pid &#61; fork();if(pid !&#61; 0) // 父进程&#xff1a;pid等于子进程id{pthread_mutex_lock(&mutex); // 加锁for(int i &#61; 0; i < 3; i&#43;&#43;){printf(" fun::父进程 【f_f%d】:id &#61; %d\n",i&#43;1,getpid());sleep(1);}pthread_mutex_unlock(&mutex); // 解锁}else{// 注意&#xff1a;这里没加锁&#xff0c;因为子进程复制的父进程此时是处于加锁状态的&#xff0c;如果这里加锁会产生死锁for(int i &#61; 0; i < 3; i&#43;&#43;){printf(" fun::子进程 【f_c%d】:id &#61; %d\n",i&#43;1,getpid());sleep(1);}}
}int main()
{pthread_mutex_init(&mutex,NULL);pthread_t id; // 创建线程并调用工作线程int res &#61; pthread_create(&id,NULL,Thread_fun,NULL);assert(res &#61;&#61; 0); // 返回值为0&#xff0c;表示创建成功pthread_mutex_lock(&mutex); // 加锁&#xff0c;保证主线程先执行输出操作for(int i &#61; 0; i < 3; i&#43;&#43;){printf("main::主线程 【m %d】:id &#61; %d\n",i&#43;1,getpid());sleep(1);}printf("main is stop ... \n");pthread_mutex_unlock(&mutex); // 解锁&#xff0c;释放输出缓冲区资源// 等待工作线程结束&#xff0c;在此阻塞pthread_join(id,NULL);// 销毁互斥锁pthread_mutex_destroy(&mutex);exit(0);
}
执行结果&#xff1a;
main::主线程 【m 1】:id &#61; 75080
main::主线程 【m 2】:id &#61; 75080
fun::子进程 【f_c1】:id &#61; 75082
main::主线程 【m 3】:id &#61; 75080
fun::子进程 【f_c2】:id &#61; 75082
fun::子进程 【f_c3】:id &#61; 75082
main is stop …
fun::父进程 【f_f1】:id &#61; 75080
fun::父进程 【f_f2】:id &#61; 75080
fun::父进程 【f_f3】:id &#61; 75080
可以看到&#xff0c;在父进程中&#xff0c;遵循先答应main的内容&#xff0c;在打印线程函数。&#xff08;main is stop … 之后才执行fun::父进程&#xff09;。但是由于子进程中临界区没有加锁&#xff0c;子进程的输出不受限制&#xff0c;干扰了主进程中的main的输出。
如果我们给子进程中的临界区加锁又会产生另一种问题。&#xff08;死锁&#xff09;以下代码仅修改了子进程的临界区进行加锁操作。
#include
#include
#include
#include
#include
#include
/* 功能说明&#xff1a;* 主函数 ---- ------main:主线程---> 打印&#xff1a;main&#xff1a;主线程id&#61;..* 工作线程 --fork() ---fun&#xff1a;父进程---> 打印&#xff1a; fun&#xff1a;父进程id&#61;.. * |---fun&#xff1a;子进程---> 打印&#xff1a; fun: 子进程id&#61;..** 在工作线程中fork&#xff0c;产生的子进程&#xff0c;只会执行工作线程内的代码。但子进程会复制父进程的状态&#xff0c;如加锁。*** 加锁&#xff1a;保证先让main中的打印先完成* 问题&#xff1a;fork会复制父进程中的加锁状态&#xff0c;即在工作线程中&#xff0c;子进程已经是处于加锁状态的&#xff0c;再次加锁无法完成&#xff0c;则产生死锁。*/// 注意&#xff1a;以下代码子进程会产生死锁
void *Thread_fun()
{sleep(1); // 保证让主进程先获得锁pid_t pid &#61; fork();if(pid !&#61; 0) // 父进程&#xff1a;pid等于子进程id{pthread_mutex_lock(&mutex); // 加锁for(int i &#61; 0; i < 3; i&#43;&#43;){printf(" fun::父进程 【f_f%d】:id &#61; %d\n",i&#43;1,getpid());sleep(1);}pthread_mutex_unlock(&mutex); // 解锁}else{/* 这里产生死锁&#xff0c;复制的父进程中加锁状态,* 但是线程内使用fork不会再执行父进程中的代码&#xff0c;因此..*/pthread_mutex_lock(&mutex); // 加锁for(int i &#61; 0; i < 3; i&#43;&#43;){printf(" fun::子进程 【f_c%d】:id &#61; %d\n",i&#43;1,getpid());sleep(1);}pthread_mutex_unlock(&mutex); // 解锁&#xff0c;释放输出缓冲区资源}
}int main()
{pthread_mutex_init(&mutex,NULL);pthread_t id; // 创建线程并调用工作线程int res &#61; pthread_create(&id,NULL,Thread_fun,NULL);assert(res &#61;&#61; 0); // 返回值为0&#xff0c;表示创建成功pthread_mutex_lock(&mutex); // 加锁&#xff0c;保证主线程先执行输出操作for(int i &#61; 0; i < 3; i&#43;&#43;){printf("main::主线程 【m %d】:id &#61; %d\n",i&#43;1,getpid());sleep(1);}printf("main is stop ... \n");pthread_mutex_unlock(&mutex); // 解锁&#xff0c;释放输出缓冲区资源// 等待工作线程结束&#xff0c;在此阻塞pthread_join(id,NULL);// 销毁互斥锁pthread_mutex_destroy(&mutex);exit(0);
}
输出结果&#xff1a;
main::主线程 【m 1】:id &#61; 75247
main::主线程 【m 2】:id &#61; 75247
main::主线程 【m 3】:id &#61; 75247
main is stop …
fun::父进程 【f_f1】:id &#61; 75247
fun::父进程 【f_f2】:id &#61; 75247
fun::父进程 【f_f3】:id &#61; 75247
可以看到子进程中&#xff0c;并没有执行打印操作。使用ps
查看&#xff0c;发现有进程还未退出&#xff0c;正是我们的子进程。
父进程执行过程大致如下&#xff1a;
子进程中&#xff0c;因为工作线程以外的部分不继续执行了&#xff0c;则工作线程一直处于阻塞状态。
解决方案&#xff1a;
确保fork()时&#xff0c;父进程是没有加锁的。因为本程序需要实现先打印main的内容&#xff0c;因此我们可以这样做&#x1f447;&#xff0c;确保main中先打印并且不会占用占用锁资源。
// 尝试加锁&#xff0c;如果可以加锁&#xff0c;则证明父进程没有占用锁资源
pid_t pid &#61; fork();
// 解锁
代码如下&#xff1a;
#include
#include
#include
#include
#include
#include
{pthread_mutex_lock(&mutex); // 加锁&#xff0c;保证临界区锁以释放&#xff0c;不会被fork复制到子进程中阻塞临界区pid_t pid &#61; fork();pthread_mutex_unlock(&mutex); // 解锁if(pid !&#61; 0) // 父进程&#xff1a;pid等于子进程id{pthread_mutex_lock(&mutex); // 加锁for(int i &#61; 0; i < 3; i&#43;&#43;){printf(" fun::父进程 【f_f%d】:id &#61; %d\n",i&#43;1,getpid());sleep(1);}pthread_mutex_unlock(&mutex); // 解锁}else{pthread_mutex_lock(&mutex); // 加锁for(int i &#61; 0; i < 3; i&#43;&#43;){printf(" fun::子进程 【f_c%d】:id &#61; %d\n",i&#43;1,getpid());sleep(1);}pthread_mutex_unlock(&mutex); // 解锁}
}int main()
{pthread_mutex_init(&mutex,NULL);pthread_t id; // 创建线程并调用工作线程int res &#61; pthread_create(&id,NULL,Thread_fun,NULL);assert(res &#61;&#61; 0); // 返回值为0&#xff0c;表示创建成功pthread_mutex_lock(&mutex); // 加锁&#xff0c;保证主线程先执行输出操作for(int i &#61; 0; i < 3; i&#43;&#43;){printf("main::主线程 【m %d】:id &#61; %d\n",i&#43;1,getpid());sleep(1);}printf("main is stop ... \n");pthread_mutex_unlock(&mutex); // 解锁&#xff0c;释放输出缓冲区资源// 等待工作线程结束&#xff0c;在此阻塞pthread_join(id,NULL);// 销毁互斥锁pthread_mutex_destroy(&mutex);exit(0);
}
输出结果&#xff1a;
main::主线程 【m 1】:id &#61; 75807
main::主线程 【m 2】:id &#61; 75807
main::主线程 【m 3】:id &#61; 75807
main is stop …
fun::父进程 【f_f1】:id &#61; 75807
fun::子进程 【f_c1】:id &#61; 75811
fun::子进程 【f_c2】:id &#61; 75811
fun::父进程 【f_f2】:id &#61; 75807
fun::子进程 【f_c3】:id &#61; 75811
fun::父进程 【f_f3】:id &#61; 75807
如果我们有很多不同的锁&#xff0c;按照我们之前的解决方案&#xff0c;我们需要在 fork 之前进行 n 多次的试探动作&#xff0c;不仅麻烦还极容易引起死锁。而系统为我们提供了一个函数可以解决这个问题。
#include
#include
#include
#include
#include
#include
pthread_mutex_t lock; // 创建互斥锁void prepare() {pthread_mutex_lock(&mutex); // 加锁printf("prepare: 加锁 ...\n");
}void parent() {pthread_mutex_unlock(&mutex);printf("parent : 父进程解锁 ...\n");
}void child() {pthread_mutex_unlock(&mutex);printf("child : 子进程解锁 ...\n");
}void *Thread_fun(void*arg)
{sleep(1);pthread_mutex_lock(&lock); // 加锁for(int i &#61; 0; i < 5; i&#43;&#43;){printf(" fun::%s 【f_f%d】:id &#61; %d\n",(char*)arg,i&#43;1,getpid());sleep(1);}pthread_mutex_unlock(&lock); // 解锁
}int main()
{pthread_mutex_init(&mutex,NULL);pthread_mutex_init(&lock,NULL);pthread_t id; // 创建线程并调用工作线程// 注册forkint res &#61; pthread_atfork(prepare, parent, child);assert(res &#61;&#61; 0);char buff[] &#61; "父进程";int ret &#61; pthread_create(&id,NULL,Thread_fun,(void*)buff);assert(ret &#61;&#61; 0); // 返回值为0&#xff0c;表示创建成功/* fork调用前调用preoarefork调用完&#xff0c;在调用child和parent*/pid_t pid &#61; fork();/*在父进程中&#xff08;当前进程中&#xff09;调用fork。经由 fork() 创建的子进程&#xff0c;其中只有一个线程&#xff08;调用fork的线程&#xff0c;即主线程&#xff09;因此&#xff0c;fork产生的子进程中&#xff0c;不会执行Thread_fun()函数&#xff0c;则我们可以在父进程&#xff08;当前进程中手动调用&#xff09;*/if(pid &#61;&#61; 0) // 子进程{sleep(1); // 保证main中父进程先执行// 注&#xff1a;这里的子进程与父进程是两个进程&#xff0c;不共享锁。同步需要设置管道或者信号量等 // ********strcpy(buff,"子进程");Thread_fun((void*)buff);}else // 父进程{pthread_mutex_lock(&lock); // 加锁for(int i &#61; 0; i < 10; i&#43;&#43;){printf("main::主函数 【f_f%d】:id &#61; %d\n",i&#43;1,getpid());sleep(1);}printf("main is stop ... \n");pthread_mutex_unlock(&lock); // 解锁}// 等待工作线程结束&#xff0c;在此阻塞pthread_join(id,NULL);// 销毁互斥锁pthread_mutex_destroy(&mutex);pthread_mutex_destroy(&lock);exit(0);
}
输出&#xff1a;
父进程&#xff1a;先输出 main中的&#xff0c;再输出工作线程中的
子进程&#xff1a;只有一个线程&#xff0c;在main中&#xff0c;通过手动调用执行工作函数。由于进程间全局变量&#xff08;我们定义的锁&#xff09;不共享&#xff0c;所以子进程在输出时不受锁影响。如有需要加入型号量等。。